package comnet.ftpserver;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Mikko Puustinen (mikko.puustinen@gmail.com)
 * @version 0.1, 2008-jan-08
 */
public class UserHandler implements Runnable {
    private Socket socket;
    private BufferedReader in = null;
    private PrintWriter out = null;
    private static final String WELCOME_MSG = "Welcome to the server, enjoy your stay";
    private File rootFolder;
    private File currentFolder;

    public UserHandler(Socket socket, File rootFolder) throws IOException {
        this.socket = socket;
        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
        this.rootFolder = rootFolder;
        this.currentFolder = rootFolder;
        sendOk(WELCOME_MSG);
    }

    /**
     *
     */
    public void run() {
        String line;

        try {
            while ((line = in.readLine()) != null) {
                System.err.println(">> " + line);
                if (line.matches("(?i)CD\\s.+")) {
                    String[] str = line.split(" ", 2);
                    changeDirectory(new File(currentFolder, str[1]));
                } else if (line.matches("(?i)LIST")) {
                    sendFileListing(openConnection());
                } else if (line.matches("(?i)GET\\s.+")) {
                    String[] str = line.split(" ", 2);
                    File file = new File(currentFolder, str[1]);

                    if (file.isFile()) {
                        if (isValidDirectory(file)) {
                            sendFile(openConnection(), file);
                        } else {
                            sendError("Permission denied");
                        }
                    } else {
                        sendError("File not found");
                    }
                } else if (line.matches("(?i)PUT\\s.+")) {
                    String[] str = line.split(" ", 2);
                    String filename = str[1];
                    File file;

                    if ((file = new File(rootFolder + "\\upload", filename)).exists()) {
                        sendError("File exists");
                    } else {
                        if (isValidDirectory(file)) {
                            receiveFile(openConnection(), file);
                        } else {
                            sendError("Permission denied");
                        }
                    }
                } else if (line.matches("(?i)QUIT")) {
                    sendOk("Bye bye...");
                    break;
                } else {
                    sendError("Command not recognized or missing parameters.");
                }
            }
        } catch (IOException e) {
            //
        } finally {
            System.out.println("Client disconnected.");
            try {
                in.close();
                out.close();
                socket.close();
            } catch (IOException e) {
                //
            }
        }
    }

    /**
     * @param file
     */
    private void changeDirectory(File file) {
        if (file.isDirectory()) {
            if (!isValidDirectory(file)) {
                sendError("Permission denied");
                return;
            }
            try {
                currentFolder = file.getCanonicalFile();
            } catch (IOException e) {
                sendError("Could not change directory. Try again later");
                return;
            }
            sendOk();
        } else {
            sendError("No such folder");
        }
    }

    /**
     * @param dataSocket
     *
     * @throws IOException
     */
    private void sendFileList(Socket dataSocket) throws IOException {
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(dataSocket.getOutputStream()), true);
        File[] files = currentFolder.listFiles();
        StringBuffer listing = new StringBuffer();

        for (File f : files) {
            String str;

            if (f.isDirectory()) {
                str = "/" + f.getName();
            } else {
                str = f.getName();
            }

            listing.append(str).append(":");
        }

        pw.println(listing.toString());
    }

    /**
     * @param dataSocket
     *
     * @throws IOException
     * @throws IllegalArgumentException
     */
    //FIXME: Almost too ugly to be true
    private void sendFileListing(Socket dataSocket) throws IOException, IllegalArgumentException {
        if (dataSocket == null) {
            throw new IllegalArgumentException();
        }
        File tempfile = File.createTempFile("filelist", null);
        OutputStream os = new BufferedOutputStream(new FileOutputStream(tempfile));
        File[] files = currentFolder.listFiles();

        for (File f : files) {
            String str;

            if (f.isDirectory()) {
                str = "/" + f.getName();
            } else {
                str = f.getName();
            }
            str += ":";
            os.write(str.getBytes());
        }
        os.flush();
        os.close();
        sendFile(dataSocket, tempfile);
        tempfile.delete();
    }

    /**
     * @param dataSocket
     * @param file
     *
     * @throws IOException
     */
    private void sendFile(Socket dataSocket, File file) throws IOException {
        int numberOfBytes = (int)file.length();
        DataOutputStream os = new DataOutputStream(dataSocket.getOutputStream());
        BufferedInputStream is = null;

        os.writeInt(numberOfBytes);
        os.writeInt(0x20);

        try {
            is = new BufferedInputStream(new FileInputStream(file));
            fileHandling(is, os, numberOfBytes, dataSocket.getSendBufferSize());
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

    /**
     * @param dataSocket
     * @param file
     *
     * @throws IOException
     */
    private void receiveFile(Socket dataSocket, File file) throws IOException {
        DataInputStream input;
        BufferedOutputStream os = null;
        int numberOfBytes;

        input = new DataInputStream(dataSocket.getInputStream());
        numberOfBytes = input.readInt();
        input.readInt();

        try {
            os = new BufferedOutputStream(new FileOutputStream(file));
            fileHandling(input, os, numberOfBytes, dataSocket.getReceiveBufferSize());
        } finally {
            if (os != null) {
                os.close();
            }
        }
    }

    private void fileHandling(InputStream is, OutputStream os, final int numberOfBytes, int bufferSize) throws IOException {
        byte[] buffer = new byte[bufferSize];
        int bytesRead = 0;
        int length;

        while ((length = is.read(buffer)) > 0 && bytesRead < numberOfBytes) {
            os.write(buffer, 0, length);
            bytesRead += length;
        }

        os.flush();
        os.close();
        is.close();
    }

    public boolean isValidDirectory(File file) {
        return isValidDirectory(rootFolder, file);
    }

    /**
     * @param rootDir
     * @param file
     *
     * @return
     */
    public boolean isValidDirectory(final File rootDir, File file) {
        String dir;
        String root;

        try {
            dir = file.getCanonicalPath();
            root = rootDir.getCanonicalPath();
        } catch (IOException e) {
            System.err.println("Exception in UserHandler.isValidDirectory()");
            dir = ".";
            root = null;
        }

        return dir.startsWith(root);
    }

    private void sendOk() {
        out.println("OK");
        System.err.println("<< OK");
    }

    private void sendOk(String message) {
        out.println("OK " + message);
        System.err.println("<< OK " + message);
    }

    private void sendError(String message) {
        out.println("ERROR " + message);
        System.err.println("<< ERROR " + message);
    }

    private Socket openConnection() throws IOException {
        ServerSocket ss = new ServerSocket();
        Socket dataSocket;

        ss.bind(null);
        sendOk(String.valueOf(ss.getLocalPort()));
        dataSocket = ss.accept();
        ss.close();

        return dataSocket;
    }

    private Socket openConnectionInRange(int start, int end) throws IOException {
        ServerSocket ss = null;
        Socket dataSocket;

        for (int i = start; i < end; i++) {
            try {
                ss = new ServerSocket(i);
                break;
            } catch (IOException e) {
                //e.printStackTrace();
            }
        }

        sendOk(String.valueOf(ss.getLocalPort()));
        dataSocket = ss.accept();
        ss.close();

        return dataSocket;
    }
}
